package actor

import (
	"context"
	"fmt"
	"sync"
	"time"

	proto "xagent/proto/v1"

	"github.com/sirupsen/logrus"
)

// ActionContext represents the context for an action
type ActionContext struct {
	Input     interface{}
	Output    interface{}
	StartTime time.Time
	EndTime   time.Time
	Status    string
	Error     error
	Metadata  map[string]interface{}
	Logger    *logrus.Entry
	mutex     sync.RWMutex
}

// IAction represents something an actor can do
type IAction interface {
	// Name returns the action's name
	Name() string

	// Run executes the action
	Run(ctx context.Context, input interface{}) (*proto.AgentMessage, error)

	// Prerequisites returns list of required actions before this one
	Prerequisites() []string

	// GetContext returns the action's context
	GetContext() *ActionContext

	// SetContext sets the action's context
	SetContext(ctx *ActionContext)
}

// BaseAction provides a base implementation of the IAction interface
type BaseAction struct {
	name    string
	context *ActionContext
	logger  *logrus.Entry
}

// NewBaseAction creates a new base action
func NewBaseAction(name string) *BaseAction {
	return &BaseAction{
		name: name,
		context: &ActionContext{
			StartTime: time.Now(),
			Status:    "INITIALIZED",
			Metadata:  make(map[string]interface{}),
			Logger:    logrus.WithField("action", name),
		},
		logger: logrus.WithField("action", name),
	}
}

// Name returns the action's name
func (a *BaseAction) Name() string {
	return a.name
}

// Run executes the action
func (a *BaseAction) Run(ctx context.Context, input interface{}) (*proto.AgentMessage, error) {
	return nil, fmt.Errorf("Run method not implemented")
}

// Prerequisites returns list of required actions
func (a *BaseAction) Prerequisites() []string {
	return nil
}

// GetContext returns the action's context
func (a *BaseAction) GetContext() *ActionContext {
	return a.context
}

// SetContext sets the action's context
func (a *BaseAction) SetContext(ctx *ActionContext) {
	a.context = ctx
}

// AddMetadata adds metadata to the action context
func (a *BaseAction) AddMetadata(key string, value interface{}) {
	a.context.mutex.Lock()
	defer a.context.mutex.Unlock()
	a.context.Metadata[key] = value
}

// GetMetadata gets metadata from the action context
func (a *BaseAction) GetMetadata(key string) (interface{}, bool) {
	a.context.mutex.RLock()
	defer a.context.mutex.RUnlock()
	value, ok := a.context.Metadata[key]
	return value, ok
}

// SetStatus sets the action's status
func (a *BaseAction) SetStatus(status string) {
	a.context.mutex.Lock()
	defer a.context.mutex.Unlock()
	a.context.Status = status
}

// GetStatus gets the action's status
func (a *BaseAction) GetStatus() string {
	a.context.mutex.RLock()
	defer a.context.mutex.RUnlock()
	return a.context.Status
}

// SetError sets the action's error
func (a *BaseAction) SetError(err error) {
	a.context.mutex.Lock()
	defer a.context.mutex.Unlock()
	a.context.Error = err
}

// GetError gets the action's error
func (a *BaseAction) GetError() error {
	a.context.mutex.RLock()
	defer a.context.mutex.RUnlock()
	return a.context.Error
}

// GetDuration returns the action's duration
func (a *BaseAction) GetDuration() time.Duration {
	a.context.mutex.RLock()
	defer a.context.mutex.RUnlock()
	if a.context.EndTime.IsZero() {
		return time.Since(a.context.StartTime)
	}
	return a.context.EndTime.Sub(a.context.StartTime)
}

// Complete marks the action as complete
func (a *BaseAction) Complete() {
	a.context.mutex.Lock()
	defer a.context.mutex.Unlock()
	a.context.EndTime = time.Now()
	a.context.Status = "COMPLETED"
}

// Fail marks the action as failed
func (a *BaseAction) Fail(err error) {
	a.context.mutex.Lock()
	defer a.context.mutex.Unlock()
	a.context.EndTime = time.Now()
	a.context.Status = "FAILED"
	a.context.Error = err
}
